package com.woniuxy.lab.woniu_lab.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.lang.Snowflake;
import cn.hutool.core.util.RandomUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.github.dockerjava.api.command.CreateContainerResponse;
import com.woniuxy.lab.woniu_lab.exception.RangeException;
import com.woniuxy.lab.woniu_lab.exception.UserException;
import com.woniuxy.lab.woniu_lab.exception.WebException;
import com.woniuxy.lab.woniu_lab.exception.WebExceptionCode;
import com.woniuxy.lab.woniu_lab.model.*;
import com.woniuxy.lab.woniu_lab.dao.RangeDao;
import com.woniuxy.lab.woniu_lab.model.RangeUser;
import com.woniuxy.lab.woniu_lab.model.User;
import com.woniuxy.lab.woniu_lab.service.*;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.woniuxy.lab.woniu_lab.service.UserService;
import com.woniuxy.lab.woniu_lab.service.dto.*;
import com.woniuxy.lab.woniu_lab.util.*;
import com.woniuxy.lab.woniu_lab.service.dto.rangeDto.GetRangeDto;
import com.woniuxy.lab.woniu_lab.service.dto.userDto.ExperRankingsDto;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.redisson.api.RBucket;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.ObjectUtils;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.List;

import static com.woniuxy.lab.woniu_lab.exception.WebExceptionCode.*;

/**
 * <p>
 * 靶场表 服务实现类
 * </p>
 *
 * @author Silas
 * @since 2023-03-15
 */
@Service
@Slf4j
@Transactional
public class RangeServiceImpl extends ServiceImpl<RangeDao, Range> implements RangeService {

    public static final String NORMAL = "NORMAL";
    public static final String DISABLE = "DISABLE";
    public static final String ZIP = "zip";
    public static final String ENABLE = "ENABLE";
    public static final String HOST = "43.136.98.253";
    public static final String ROOT = "root";
    public static final String MNT_LOCAL_SHARE_RUNNING = "/mnt/local_share/running/range";
    public static final String PASSWORD = "woniuCQ2023!@#$%^&*()";
    public static final String STOP_DOCKER_COMPOSE_TOPIC = "stop_docker_compose_topic";
    public static final String RUNNING = "RUNNING";
    public static final String DOCKER = "DOCKER";
    public static final String VAR_WWW_HTML = "/var/www/html";



    @Value("${linux.ip}")
    private String linuxIp;
    @Value("${linux.username}")
    private String username;
    @Value("${linux.password}")
    private String password;

    @Value("${jar.ip}")
    private String host;

    @Resource
    private RedissonClient redissonClient;
    @Resource
    private UserService userService;
    @Resource
    private RangeUserService rangeUserService;
    @Resource
    private ExperService experService;
    @Resource
    private LogService logService;

    @Resource
    private RangeDao rangeDao;

    @Resource
    private RocketMQTemplate rocketMQTemplate;

    @Resource
    private PortService portService;

    @Value("${dockerHttp}")
    private String dockerIp;
    @Value("${dockerPort}")
    private String dockerPort;

    @Override
    public IPage<RangeDto> queryRangeByLevel(Integer page, Integer size, String level) {
        return getRangeDtoIPage(page, size, level,"level",LEVEL_NOT_RIGHT,LEVEL_NOT_EXISTS);
    }

    @Override
    public IPage<RangeDto> queryRangeByType(Integer page, Integer size, String type) {
        return getRangeDtoIPage(page, size, type,"range_type",TYPE_NOT_RIGHT,TYPE_NOT_EXISTS);
    }

    @Override
    public IPage<RangeDto> queryRangeByCondition(Integer page, Integer size, String condition) {
        //条件为空，查询全部
        if (ObjectUtils.isEmpty(condition)){
            Page<Range> ranges = page(new Page<>(page, size),new QueryWrapper<Range>().eq("status","ENABLE")
                    .eq("ctf_or_exper","CTF"));
            //返回 IPage<RangeDto>
            return ranges.convert(r -> {
                RangeDto rd = new RangeDto();
                BeanUtil.copyProperties(r, rd);
                return rd;
            });
        }
        //根据条件查询
        Page<Range> ranges = page(new Page<>(page, size),
                new QueryWrapper<Range>().eq("status","ENABLE")
                .eq("ctf_or_exper","CTF")
                .like("name",condition).or()
                .like("file_type",condition).or()
                .like("file_path",condition).or()
                .like("image_name",condition).or()
                .like("level",condition).or()
                .like("range_type",condition).or()
                .like("price",condition).or()
                .like("score",condition).or()
                .like("ctf_or_exper",condition).or()
                .like("exper_name",condition).or()
                .like("pass_count",condition).or()
                .like("create_count",condition)

        );
        return ranges.convert(r -> {
                    RangeDto rd = new RangeDto();
                    BeanUtil.copyProperties(r, rd);
                    return rd;
        });
    }

    @Override
    public RangeDto findRangeById(Integer rangeId) {
        Range byId = getById(rangeId);
        //判断靶场是否存在以及状态
        rangeIsExistOrNot(ObjectUtils.isEmpty(byId), DISABLE.equals(byId.getStatus()), RANGE_NOT_EXISTS);
        RangeDto rangeDto = BeanUtil.copyProperties(byId, RangeDto.class);

        List<RangeUser> rangeUserList = rangeUserService.list(new QueryWrapper<RangeUser>().eq("range_id", rangeId));

        //加入收藏数
        rangeDto.setCollectionNumber(rangeUserList.size());

        return rangeDto;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void payRange(Integer rangeId,String password) throws Exception {
        //查询靶场及校验靶场
        Range range = getById(rangeId);
        rangeIsExistOrNot(ObjectUtils.isEmpty(range), DISABLE.equals(range.getStatus()),RANGE_NOT_EXISTS);
        //校验余额
        Integer userId = JwtUtil.getUserIdFromReq();
        User user = userService.getById(userId);
        String pw = MD5Util.MD5(password);
        //判断用户是否拥有该靶场
        List<RangeUser> list = rangeUserService.list(new QueryWrapper<RangeUser>()
                .eq("range_id", rangeId)
                .eq("user_id", userId));
        if (list!=null && list.size()>0){
            throw new UserException(USER_NOT_PAY_RANGE);
        }
        //判断支付密码
        if (!pw.equals(user.getPassword())){
            throw new UserException(PWD_IS_INCORRECT);
        }
        if (user.getVip().equals(NORMAL) && user.getBalance().compareTo(range.getPrice()) < 0){
            throw new UserException(USER_BALANCE_IS_ENOUGH);
        }
        //用户扣减余额
        BigDecimal subtract = user.getBalance().subtract(range.getPrice());
        user.setBalance(subtract);
        userService.updateById(user);
        //用户更改消费总金额
        BigDecimal totalMoney = user.getTotalMoney();
        totalMoney=totalMoney.add(range.getPrice());
        user.setTotalMoney(totalMoney);

        RangeUser entity = new RangeUser();
        entity.setUserId(userId);
        entity.setRangeId(rangeId);
        rangeUserService.save(entity);
    }



    /**
     * 判断靶场是否存在及状态是否可用
     * @param byId 判断靶场是否存在
     * @param DISABLE 状态是否可用
     * @param rangeNotExists 异常
     */
    private void rangeIsExistOrNot(boolean byId, boolean DISABLE, WebExceptionCode rangeNotExists) {
        if (byId || DISABLE){
            throw new RangeException(rangeNotExists);
        }
    }

    /**
     * 单条件查询通用方法
     * @param page 页码
     * @param size 每页显示数
     * @param condition 条件
     * @param filed 条件匹配的字段
     * @param conditionNotRight 条件不存在异常
     * @param resultNotExists 结果不存在异常
     * @return IPage<RangeDto>
     */
    private IPage<RangeDto> getRangeDtoIPage(Integer page, Integer size, String condition,
                                             String filed, WebExceptionCode conditionNotRight, WebExceptionCode resultNotExists) {
        //判断条件是否存在
        rangeIsExistOrNot("".equals(condition), condition == null, conditionNotRight);

        //判断查询结果是否存在
        IPage<Range> ranges = page(new Page<>(page, size), new QueryWrapper<Range>().eq(filed, condition));
        List<Range> records = ranges.getRecords();
        rangeIsExistOrNot(records == null, records.size() == 0, resultNotExists);
        //转换为dto返回
        return ranges.convert(r -> {
            RangeDto rd = new RangeDto();
            BeanUtil.copyProperties(r, rd);
            return rd;
        });
    }


    @Override
    public IPage<GetRangeDto> getNewExperiment(IPage<Range> rangeIPage) {
        IPage<Range> range = page(rangeIPage, new QueryWrapper<Range>().
                eq("status", "ENABLE").eq("ctf_or_exper","exper").
                orderByDesc("upload_time"));
        return copyRange(range);
    }

    @Override
    public IPage<GetRangeDto> getHotExper(IPage<Range> rangeIPage) {
        IPage<Range> range = page(rangeIPage, new QueryWrapper<Range>().
                eq("ctf_or_exper", "exper").eq("status", "ENABLE").
                orderByDesc("create_count"));
        return copyRange(range);
    }

    @Override
    public IPage<GetRangeDto> getExperOrderByMoney(IPage<Range> rangeIPage, Integer sort) {
        QueryWrapper<Range> rangeQueryWrapper = new QueryWrapper<Range>().
                eq("ctf_or_exper", "exper").eq("status", "ENABLE");
        //价格倒叙
        if (sort == -1){
            rangeQueryWrapper = rangeQueryWrapper.orderByDesc("price");
            //价格正序
        }else {
            rangeQueryWrapper = rangeQueryWrapper.orderByAsc("price");
        }
        IPage<Range> range = page(rangeIPage, rangeQueryWrapper);
        return copyRange(range);
    }

    @Override
    public IPage<GetRangeDto> getExperByLevel(IPage<Range> rangeIPage, String level) {
        IPage<Range> range = page(rangeIPage, new QueryWrapper<Range>().
                eq("ctf_or_exper", "exper").eq("status", "ENABLE").
                eq("level", level).orderByDesc("upload_time"));
        return copyRange(range);
    }

    @Override
    public IPage<GetRangeDto> getExperByType(IPage<Range> rangeIPage, String rangeType) {
        IPage<Range> range = page(rangeIPage,new QueryWrapper<Range>().
                eq("ctf_or_exper", "exper").eq("status", "ENABLE").
                eq("range_type",rangeType).orderByDesc("upload_time"));
        return copyRange(range);
    }

    @Override
    public IPage<GetRangeDto> getExperByName(IPage<Range> rangeIPage, String name) {
        IPage<Range> range = page(rangeIPage,new QueryWrapper<Range>().
                eq("ctf_or_exper", "exper").eq("status", "ENABLE").
                like("name",name).orderByDesc("upload_time"));
        return copyRange(range);
    }

    @Override
    public List<ExperRankingsDto> experRankings() {
        RBucket<List<ExperRankingsDto>> bucket = redissonClient.getBucket("exper:rankings");

        if (!bucket.isExists()){
            List<User> range_score = userService.list(new QueryWrapper<User>().orderByDesc("range_score"));
            List<ExperRankingsDto> experRankingsDto = ListUtil.sub(BeanUtil.copyToList(range_score, ExperRankingsDto.class), 0, 10);

            bucket.set(experRankingsDto);
            bucket.expire(Duration.ofHours(12));
            return experRankingsDto;
        }
        return bucket.get();
    }

    @Override
    public IPage<GetRangeDto> getNewCTF(IPage<Range> rangeIPage) {
        IPage<Range> range = page(rangeIPage, new QueryWrapper<Range>().
                eq("status", "ENABLE").eq("ctf_or_exper","ctf").
                orderByDesc("upload_time"));
        return copyRange(range);
    }

    @Override
    public IPage<GetRangeDto> getHotCTF(IPage<Range> rangeIPage) {
        IPage<Range> range = page(rangeIPage, new QueryWrapper<Range>().
                eq("ctf_or_exper", "ctf").eq("status", "ENABLE").
                orderByDesc("create_count"));
        return copyRange(range);
    }

    @Override
    public IPage<GetRangeDto> getCTFOrderByMoney(IPage<Range> rangeIPage, Integer sort) {
        QueryWrapper<Range> rangeQueryWrapper = new QueryWrapper<Range>().
                eq("ctf_or_exper", "ctf").eq("status", "ENABLE");
        //价格倒叙
        if (sort == -1){
            rangeQueryWrapper = rangeQueryWrapper.orderByDesc("price");
            //价格正序
        }else {
            rangeQueryWrapper = rangeQueryWrapper.orderByAsc("price");
        }
        IPage<Range> range = page(rangeIPage, rangeQueryWrapper);
        return copyRange(range);
    }

    @Override
    public IPage<CtfLeaderBoardDto> ctfLeaderboard() {
        //redis中查询排行榜
        RBucket<IPage<CtfLeaderBoardDto>> bucket = redissonClient.getBucket("ctf:leader:user");
        //redis中无数据
        if(bucket==null || bucket.size()==0){
            //数据库查询
            IPage<User> ctfLeader = userService.page(new Page<>(1, 10),
                    new QueryWrapper<User>().orderByDesc("ctf_score"));

            //转换为dto
            IPage<CtfLeaderBoardDto> convert = ctfLeader.convert(u -> {
                CtfLeaderBoardDto cbd = new CtfLeaderBoardDto();
                BeanUtil.copyProperties(u, cbd);
                return cbd;
            });
            //写入redis
            bucket.set(convert);
            bucket.expire(Duration.ofHours(12));
            //返回给前端
            return convert;
        }
        return bucket.get();
    }

    // 首页获取最新实验
    @Override
    public IPage<ExperDto> getNewExper(Integer page, Integer size) {
        Page<Range> rangePage = new Page<>(page, size);
        IPage<Range> myPage = this.page(rangePage, new QueryWrapper<Range>().eq("status", "ENABLE").orderByDesc("upload_time"));
        return myPage.convert(p -> {
            ExperDto experDto = new ExperDto();
            BeanUtil.copyProperties(p, experDto);
            return experDto;
        });
    }

    @SneakyThrows
    @Override
    public String startup(User user, Integer rangeId) {
        // 1.获取靶场类型并校验
        Range range = this.getById(rangeId);
        if(range == null) throw new WebException(RANGE_ID_NOT_EXISTS);
        if(!range.getStatus().equals("ENABLE")) throw new WebException(RANGE_IS_NOT_AVAILABLE);
        String type = range.getFileType();// 靶场类型 docker/war/zip/other 分别对应 镜像/war/php/文件
        if(type.equals("other")) throw new WebException(RANGE_TYPE_INCORRECT);// 如果是文件类型 驳回
        if(!(type.equals("docker") || type.equals("war") || type.equals("zip"))) throw new WebException(RANGE_TYPE_IS_NOT_EXISTS);

        // 2.判断靶场是否免费 或用户是否购买或是会员
        System.out.println(range.getPrice().compareTo(BigDecimal.ZERO));
        if (range.getPrice().compareTo(BigDecimal.ZERO) != 0 && user.getVip().equals("NORMAL")){
            RangeUser rangeUser = rangeUserService.getOne(new QueryWrapper<RangeUser>().eq("user_id", user.getId()).eq("range_id", rangeId));
            if (rangeUser==null) throw new WebException(DOCKER_NOT_PAY);
        }

        // 3.判断用户是否有运行中的靶场
        List<Exper> experList = experService.list(new QueryWrapper<Exper>().eq("user_id", user.getId()).eq("status", "RUNNING").ne("file_or_docker", "FILE"));
        if (experList !=null && experList.size()>0) throw new WebException(DOCKER_IS_RUNNING);
        //4.添加创建次数
        range.setCreateCount(range.getCreateCount()+1);
        System.out.println("type:"+type);
        //返回访问路径
        String link = "";
        if(type.equals("docker")){
            // 如果启动的环境是docker
            link = startDocker(range, user);
        }else if(type.equals("war")){
            // 如果启动的环境是war
            link = startWar(range,user);
            Thread.sleep(10 * 1000);// 暂停10秒再返回值
        }else {
            // 如果启动的环境是php -> type is zip
            link = startCompose(user, range);
        }

        return link;
    }

    // 下载靶场实验文件
    @Override
    public String downloadExper(Integer userId, Integer rangeId) {
        // 获取靶场对象 判断是否是文件类型实验（非环境类型） 是否可用
        Range range = this.getById(rangeId);
        if (!"ENABLE".equals(range.getStatus())) throw new WebException(RANGE_IS_NOT_AVAILABLE);
        if (!range.getFileType().equals("other")) throw new WebException(RANGE_IS_NOT_FILE_TYPE);

        // 如果用户下载过该靶场 则直接返回下载链接，不重复记录
        Exper exp = experService.getOne(new QueryWrapper<Exper>().eq("user_id", userId).eq("range_id", rangeId));
        if (exp != null) return exp.getFilePath();

        // 创建exper实验对象
        Exper exper = new Exper();
        String no = new Snowflake(1, 1).nextIdStr();// 雪花算法生成随机编号
        exper.setNo(no);
        exper.setUserId(userId);
        exper.setCreateTime(LocalDateTime.now());
        exper.setRangeId(rangeId);
        exper.setFlag(range.getFlag());
        exper.setScore(range.getScore());
        exper.setCtfOrExper(range.getCtfOrExper());
        exper.setFilePath(range.getFilePath());
        exper.setFileOrDocker("FILE");
        exper.setVolumeOuter(range.getLPath());
        String link = range.getFilePath();
        exper.setLink(link);
        exper.setStatus("CLOSE");
        experService.save(exper);

        // 记录靶场日志
        Log log = new Log();
        log.setRangeId(rangeId);
        log.setLogTime(LocalDateTime.now());
        log.setInfo("用户"+userId+"下载靶场");
        log.setUserId(userId);
        log.setNickname(userService.getById(userId).getNickname());
        logService.save(log);

        return link;
    }


    /**
     * 复制IPage<Range>为IPage<GetRangeDto>
     * @return IPage<GetRangeDto>
     */
    public IPage<GetRangeDto> copyRange(IPage<Range> range){
        return range.convert(r ->{
            GetRangeDto g = new GetRangeDto();
            BeanUtil.copyProperties(r,g);
            return g;
        });
    }

    /**
     * 获取随机端口
     * @return Port 端口对象
     */
    public Port getLinuxIp() {
        //随机端口,查看是否重复
        Port one =new Port();
        while (true){
            String lPort = RandomUtil.randomInt(20000, 40000)+"";
            Port port = portService.getOne(new QueryWrapper<Port>().eq("port", lPort));
            if (port==null){
                one.setPort(lPort);
                one.setType("DYNAMIC");
                one.setUserId(userService.assertUser().getId());
                portService.save(one);
                break;
            }
        }
        return one;
    }

    /**
     * php+mysql5启动
     */
    @Override
    public String startCompose(User user,Range range) {
        Integer userId=user.getId();
        ShellUtil shell = ShellUtil.getShell(HOST, ROOT, PASSWORD);
        //新建用户文件夹
        shell.mkdir(MNT_LOCAL_SHARE_RUNNING+userId);
        //获取上传文件的路径
        String filePath = range.getFilePath();
        String replace = filePath.replace("nfs_share", "local_share");

        shell.unZip(MNT_LOCAL_SHARE_RUNNING+userId,replace);
        //新建mysqlinit文件夹
        shell.mkdir(MNT_LOCAL_SHARE_RUNNING+userId+"/mysqlinit");
        //将sql文件移入mysqlinit文件夹
        String sql = shell.getFileNameByCondition(MNT_LOCAL_SHARE_RUNNING + userId, "\\.sql$");
        sql=sql.replace("\n","");
        shell.mv(MNT_LOCAL_SHARE_RUNNING+userId+"/"+sql,MNT_LOCAL_SHARE_RUNNING+userId+"/mysqlinit");
        //新建web文件夹
        shell.mkdir(MNT_LOCAL_SHARE_RUNNING+userId+"/web");
        //将php文件移入web文件夹
        String php = shell.getFileNameByCondition(MNT_LOCAL_SHARE_RUNNING + userId, "\\.php$");
        String[] myArray = php.split("\n");
        for (String s : myArray) {
            shell.mv(MNT_LOCAL_SHARE_RUNNING+userId+"/"+s,MNT_LOCAL_SHARE_RUNNING+userId+"/web");
        }
        //修改对外开放端口
        Port mysqlPort = getLinuxIp();
        String port1 = mysqlPort.getPort();

        //修改mysql端口
        Port phpPort  = getLinuxIp();
        String port2 = phpPort.getPort();

        JwtUtil.modifyFile(MNT_LOCAL_SHARE_RUNNING+userId+"/docker-compose.yml","80",port1);
        JwtUtil.modifyFile(MNT_LOCAL_SHARE_RUNNING+userId+"/docker-compose.yml","3307",port2);


        //访问路径写入数据库
        range.setUri(HOST+":"+port1);
        updateById(range);
        //启动compose
        shell.startCompose(MNT_LOCAL_SHARE_RUNNING+userId);


        //写入实验表
        Exper exper = new Exper();
        exper.setRangeId(range.getId());
        //生成雪花ID
        Snowflake snowflake = new Snowflake(1, 1);
        exper.setNo(snowflake.nextIdStr());
        exper.setUserId(userId);
        exper.setCreateTime(LocalDateTime.now());
        exper.setRangeId(range.getId());
        exper.setFlag(range.getFlag());
        exper.setReleaseTime(LocalDateTime.now().plusMinutes(59));
        exper.setStatus(RUNNING);
        exper.setScore(range.getScore());
        exper.setPortOuter(port1);
        exper.setPortInner("80");
        exper.setVolumeOuter(MNT_LOCAL_SHARE_RUNNING+userId+"/web");
        exper.setVolumeInner(VAR_WWW_HTML);
        exper.setCtfOrExper(range.getCtfOrExper());
        exper.setFilePath(range.getFilePath());
        exper.setFileOrDocker(DOCKER);
        exper.setLink(HOST+":"+port1);
        exper.setRangeId(range.getId());
        experService.save(exper);

        MqStopDockerComposeDto mqStopDockerComposeDto = BeanUtil.copyProperties(exper, MqStopDockerComposeDto.class);

        //定时一小时关闭
        Message<MqStopDockerComposeDto> message = MessageBuilder.withPayload(mqStopDockerComposeDto).build();
        rocketMQTemplate.syncSend(STOP_DOCKER_COMPOSE_TOPIC,message,3000,17);
        //异步写入日志表
        logService.greatLog(user, range,"php启动");
        return HOST+":"+port1;
    }



    public String startWar(Range range,User user){
        //创建shell连接
        ShellUtil shell = ShellUtil.getShell(HOST, ROOT, PASSWORD);
        //启动的地址
        String rangePath = "/mnt/local_share/running"+"/range"+user.getId();
        //创建文件
        shell.mkdir(rangePath);
        //解压
        String replace = range.getFilePath().replace("nfs_share", "local_share");
        shell.unZip(rangePath,replace);
        //获取需要修改的Volumes,并修改
        List<String> yamlVolumes = shell.getYamlVolumes(rangePath+"/docker-compose.yaml");
        for (String volume : yamlVolumes) {
            JwtUtil.modifyFile(rangePath+"/docker-compose.yaml",volume,rangePath);
        }
        //获取需要修改的端口
        List<String> yamlPorts = shell.getYamlPorts(rangePath + "/docker-compose.yaml");
        //获取暴露出的端口
        Integer revealPort = null;
        for (String yamlPort : yamlPorts) {
            Port port = getLinuxIp();
            if(revealPort==null){
                revealPort = Integer.valueOf(port.getPort());
            }
            shell.updatePort(yamlPort,port.getPort(),rangePath + "/docker-compose.yaml");
        }
        //启动war
        shell.startCompose(rangePath);

        Exper exper = getExper(range, user, rangePath, revealPort);
        //设置访问路径
        if (range.getUri()!=null)exper.setLink(HOST+":"+revealPort+range.getUri());
        else exper.setLink(HOST+":"+revealPort);
        //保存
        experService.save(exper);

        //消息队列,1个小时取消并关闭
        MqStopDockerComposeDto mq = new MqStopDockerComposeDto();
        mq.setId(exper.getId());
        mq.setReleaseTime(exper.getReleaseTime());
        Message<MqStopDockerComposeDto> message = MessageBuilder.withPayload(mq).build();
        rocketMQTemplate.syncSend(STOP_DOCKER_COMPOSE_TOPIC,message,3000,17);
        //返回访问路径
        return exper.getLink();
    }

    private Exper getExper(Range range, User user, String rangePath, Integer revealPort) {
        Exper exper = new Exper();
        //添加实验编号
        String no = new Snowflake(1, 1).nextIdStr();
        exper.setNo(no);
        //设置userid
        exper.setUserId(user.getId());
        //设置创建时间
        exper.setCreateTime(LocalDateTime.now());
        //设置ranggeid
        exper.setRangeId(range.getId());
        //设置答案
        exper.setFlag(range.getFlag());
        //设置取消时间未一小时后
        exper.setReleaseTime(LocalDateTime.now().plusHours(1));
        //设置运行状态未运行
        exper.setStatus("RUNNING");
        //设置积分
        exper.setScore(range.getScore());
        //外部端口
        exper.setPortOuter(revealPort +"");
        //内部端口
        exper.setPortInner(range.getPort());
        //外部卷
        exper.setVolumeOuter(rangePath);
        //内部卷
        exper.setVolumeInner(range.getPath());
        //ctf或者
        exper.setCtfOrExper(range.getCtfOrExper());
        //文件路径
        exper.setFilePath(range.getFilePath());
        //
        exper.setFileOrDocker("DOCKER");
        //保存日志
        addLog(range, user,"启动war环境");
        return exper;
    }

    // 启动docker镜像
    public String startDocker(Range range,User user){
        String link = ""; // 返回的link
        // 1.获取要启动的容器的镜像
        String imageName = range.getImageName();// 镜像名 例如hello-world:latest
        DockerUtil dockerUtil = DockerUtil.getInstance(dockerIp, dockerPort);
        Boolean exist = dockerUtil.assertImageExist(imageName);
        if (!exist) throw new WebException(DOCKER_IS_NOT_EXISTS);

        // 2.生成外部端口 并放开该端口
        Port port = new Port();
        ShellUtil shell = ShellUtil.getShell(linuxIp, username, password);
        while (true){
            String lPort = RandomUtil.randomInt(20000, 40000)+"";// 随机生成一个外部端口
            Port one = portService.getOne(new QueryWrapper<Port>().eq("port", lPort));// 从数据库查询是否有存在的端口
            if (one != null) continue;// 已存在端口 继续随机取端口
            port.setPort(lPort);
            port.setType("DYNAMIC");
            port.setUserId(user.getId());
            shell.letGoPort(lPort); //放开端口
            portService.save(port);
            break;
        }
        // 3.由镜像启容器
        String containerName = user.getId() + "_" + new Date().getTime() + imageName.replace(":", "");// 容器名 用户名+时间戳+镜像名
        CreateContainerResponse container = dockerUtil.createContainer(
                imageName, range.getPort(), port.getPort(), containerName,
                null, null, range.getLPath(), range.getPath());// 创建容器
        try {
            dockerUtil.startContainerById(container.getId());
        } catch (Exception e) {
            log.warn("容器启动失败");
            shell.closePort(port.getPort());//关闭端口
            portService.remove(new QueryWrapper<Port>().eq("port", port.getPort()));//删除端口
            throw new WebException(DOCKER_START_FILE);
        }
        // 4.启动成功 创建实验表
        Exper exper = new Exper();// 以下字段暂时为null -> releaseTime/result
        String no = new Snowflake(1, 1).nextIdStr();// 雪花算法生成随机编号
        exper.setNo(no);// 实验编号
        exper.setUserId(user.getId());
        exper.setRangeId(range.getId());
        exper.setFlag(range.getFlag());
        exper.setStatus("RUNNING");
        exper.setScore(range.getScore());
        exper.setPortInner(range.getPort());
        exper.setVolumeInner(range.getPath());
        exper.setCtfOrExper(range.getCtfOrExper());
        exper.setFileOrDocker("DOCKER");
        exper.setContainerId(container.getId());// 容器id 启动成功后获取
        exper.setPortOuter(port.getPort());// 外部端口 随机生成
        exper.setVolumeOuter(range.getLPath());// 外部卷路径
        exper.setCreateTime(LocalDateTime.now());// 创建时间
        exper.setLink(link);// 靶场访问路径 实验启动后生成
        exper.setReleaseTime(LocalDateTime.now().plusHours(1));// 设置释放时间
        if (range.getUri() != null){
            link = dockerIp+":"+port.getPort()+range.getUri();
        }else {
            link = dockerIp+":"+port.getPort();
        }

        experService.save(exper);
        // 5.靶场启动次数+1
        range.setCreateCount(range.getCreateCount()+1);
        updateById(range);
        // 6.消息队列延时
        try {
            RmRangeDto rmRangeDto = new RmRangeDto();
            rmRangeDto.setRangeId(range.getId());
            rmRangeDto.setPort(port.getPort());
            rmRangeDto.setNo(exper.getNo());
            rmRangeDto.setContainerName(containerName);
            rmRangeDto.setUserId(user.getId());
            rmRangeDto.setContainerId(exper.getContainerId());
            //一个小时后关闭
            Message<RmRangeDto> message = MessageBuilder.withPayload(rmRangeDto).build();
            //正式放开
            rocketMQTemplate.syncSend("stop_container_topic",message,3000,17);
        } catch (Exception e) {
            dockerUtil.stopContainer(container.getId());
            e.printStackTrace();// 打印堆栈信息
            shell.closePort(port.getPort());//关闭端口
            portService.remove(new QueryWrapper<Port>().eq("port",port.getPort()));//删除端口
            throw new WebException(DOCKER_START_FILE);
        }
        //  7.记录靶场日志
        addLog(range, user,"启动docker镜像");
        return link;

    }

    /**
     * 添加启动记录
     */
    private void addLog(Range range, User user,String info) {
        Log log = new Log();
        log.setRangeId(range.getId());
        log.setLogTime(LocalDateTime.now());
        log.setInfo(info + range.getName());
        log.setUserId(user.getId());
        log.setTeacherId(range.getTeacherId());
        log.setNickname(user.getNickname());
        //保存日志
        logService.save(log);
    }

//    private String local2http(){
//        // /mnt/nfs_share/upload/files/1680080794728_命令.txt
//    }

}
